package testing;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;

import data.DataInfo;

public class ThreadedPredictionTester {

	// Training data
	public static int[] trainingUserIDs;

	public static short[] trainingMoviesIDs;

	public static byte[] trainingRatings;

	private static boolean[] done;

	private static double[] errors;

	private static final int NUM_THREADS = 8;

	public static int numComplete = 0;

	private static boolean debugInfo = false;

	// 1425333 is the number of ratings in the probe file
	private static int numProbe = 1408395;

	public static void loadTrainingData(boolean userIndexed) {
		String filename = DataInfo.downloadFolder + "betterprobe.txt";
		if (userIndexed) {
			filename = DataInfo.downloadFolder + "betterprobeuserindexed.txt";
		}
		System.out.println("Using probe file: " + filename);
		File probeFile = new File(filename);

		Notifier n = new Notifier(numProbe, "Loading probe set: [", "]");
		int count = 0;

		trainingUserIDs = new int[numProbe];

		trainingMoviesIDs = new short[numProbe];

		trainingRatings = new byte[numProbe];

		errors = new double[numProbe];
		done = new boolean[numProbe];
		Arrays.fill(done, false);

		try {
			BufferedReader in = new BufferedReader(new FileReader(probeFile));
			if (!in.ready())
				throw new IOException();
			// First line is the movie number: "200:\n"

			String line = "";
			// Get the movieID only
			short movieID = 0;
			int userID = 0;
			// System.out.print("Movie: " + movieID);
			while ((line = in.readLine()) != null) {
				// If the line is a new movieID
				if (line.contains(":")) {
					if (userIndexed) {
						userID = Integer.parseInt(line.substring(0, line
								.length() - 1));
					} else {
						movieID = Short.parseShort(line.substring(0, line
								.length() - 1));
					}
				} else {
					// Create the ratings
					ArrayList<String> tokens = PredictionTester.splitLine(line,
							',');
					if (userIndexed) {
						movieID = Short.parseShort(tokens.get(1));
					} else {
						userID = Integer.parseInt(tokens.get(1));
					}
					byte rating = Byte.parseByte(tokens.get(0));
					if (userID < 1) {
						System.err.println("Invalid user ID: " + userID);
						System.err.println("Line: " + line);
					}
					// Store the rating in memory
					trainingUserIDs[count] = userID;

					trainingMoviesIDs[count] = movieID;

					trainingRatings[count] = rating;
					n.next();
					count++;
				}

			}

			in.close();
		} catch (IOException e) {
			System.out.println(e);
		}
		System.out.println("Loaded " + count + " entries in the probe set.");
	}

	public static double getProbeError(NetflixPredictor predictionClass,
			boolean userIndexed, boolean debug) {
		debugInfo = debug;
		loadTrainingData(userIndexed);
		int fetch = (numProbe / NUM_THREADS) + 1;

		Thread[] threads = new Thread[NUM_THREADS];

		System.out.print("Building threads...");
		for (int i = 0; i < NUM_THREADS; i++) {
			ArrayList<Integer> todo = new ArrayList<Integer>();
			int start = i * fetch;
			for (int j = 0; j < fetch && start + j < numProbe; j++) {
				todo.add(start + j);
			}
			threads[i] = new Thread(new PredictionThread(predictionClass, todo));
			threads[i].start();
		}
		System.out.println("done");

		// Wait for all to finish
		while (numComplete < numProbe) {

			try {
				System.out.println("waiting... (" + numComplete + ")");
				Thread.sleep(10000);
			} catch (InterruptedException e1) {
				e1.printStackTrace();
			}
		}

		// Calc the RMSE
		double sum = 0;
		for (double e : errors) {
			sum += e * e;
		}

		return Math.sqrt(sum / numProbe);
	}

	public static synchronized void addError(int i, double d) {
		errors[i] = d;
		done[i] = true;
		numComplete++;
		if (debugInfo && numComplete % 100 == 0) {
			printCurrentError();
		}
	}

	public static synchronized void printCurrentError() {
		double sum = 0;
		int count = 0;
		for (int i = 0; i < numProbe; i++) {
			if (done[i]){
				sum += errors[i] * errors[i];
				count++;
			}
		}
		System.out.println(count + ": " + Math.sqrt(sum / count));
	}

}
